package core

import (
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"

	"codeberg.org/vnpower/pixivfe/v2/audit"
	"codeberg.org/vnpower/pixivfe/v2/config"
	"codeberg.org/vnpower/pixivfe/v2/i18n"
	"codeberg.org/vnpower/pixivfe/v2/server/session"
	"github.com/goccy/go-json"
)

type TagDetail struct {
	Name            string `json:"tag"`
	AlternativeName string `json:"word"`
	Metadata        struct {
		Detail      string      `json:"abstract"`
		Image       string      `json:"image"` // The original image URL as returned by the API
		ImageMaster string      // The higher resolution version obtained by transformImageURL
		Name        string      `json:"tag"`
		ID          json.Number `json:"id"`
	} `json:"pixpedia"`
}

type SearchArtworks struct {
	Artworks []ArtworkBrief `json:"data"`
	Total    int            `json:"total"`
}

// TODO: find a better name for this struct
type TagTag struct {
	Name           string `json:"tag"`
	TranslatedName string `json:"translation"`
}

type SearchResult struct {
	Artworks SearchArtworks
	Popular  struct {
		Permanent []ArtworkBrief `json:"permanent"`
		Recent    []ArtworkBrief `json:"recent"`
	} `json:"popular"`
	RelatedTags []TagTag `json:"relatedTags"`
}

type SearchPageSettings struct {
	Name     string // Tag to search for
	Category string // Filter by type, could be illusts or mangas
	Order    string // Sort by date
	Mode     string // Safe, R18 or both
	Ratio    string // Landscape, portrait, or squared
	Page     string // Page number

	// To implement
	Smode string // Exact match, partial match, or match with title
	Wlt   string // Minimum image width
	Wgt   string // Maximum image width
	Hlt   string // Minimum image height
	Hgt   string // Maximum image height
	Tool  string // Filter by production tools (ex. Photoshop)
	Scd   string // After this date
	Ecd   string // Before this date
}

func (s SearchPageSettings) ReturnMap() map[string]string {
	return map[string]string{
		"Name":     s.Name,
		"Category": s.Category,
		"Order":    s.Order,
		"Mode":     s.Mode,
		"Ratio":    s.Ratio,
		"Smode":    s.Smode,
		"Wlt":      s.Wlt,
		"Wgt":      s.Wgt,
		"Hlt":      s.Hlt,
		"Hgt":      s.Hgt,
		"Scd":      s.Scd,
		"Ecd":      s.Ecd,
		"Tool":     s.Tool,
		"Page":     s.Page,
	}
}

// transformImageURL replaces the "/c/{parameters}/" segment in the image URL with "/".
//
// Regex matching is used instead of simple substring replacement for flexibility.
func transformImageURL(auditor *audit.Auditor, inputURL string) (string, error) {
	auditor.SugaredLogger.Debugf("transformImageURL provided with original URL: %s", inputURL)

	// Parse the URL to ensure it's valid and to manipulate its path
	parsedURL, err := url.Parse(inputURL)
	if err != nil {
		auditor.SugaredLogger.Errorf("transformImageURL found invalid URL '%s': %v", inputURL, err)
		return "", err
	}

	// Define a regex to match the "/c/{parameters}/" segment and replace it with "/"
	re := regexp.MustCompile(`/c/[^/]+/`)
	updatedPath := re.ReplaceAllString(parsedURL.Path, "/")

	// Update the path in the parsed URL and reconstruct the full URL
	parsedURL.Path = updatedPath
	updatedURL := parsedURL.String()

	auditor.SugaredLogger.Debugf("transformImageURL returned transformed URL: %s", updatedURL)

	return updatedURL, nil
}

func GetTagData(auditor *audit.Auditor, r *http.Request, name string) (TagDetail, error) {
	var tag TagDetail

	URL := GetTagDetailURL(name)

	resp, err := API_GET_UnwrapJson(r.Context(), auditor, URL, "", r.Header)
	if err != nil {
		return tag, err
	}

	resp, err = session.ProxyImageUrl(r, resp)
	if err != nil {
		return tag, err
	}

	err = json.Unmarshal([]byte(resp), &tag)
	if err != nil {
		return tag, err
	}

	// Call transformImageURL to obtain a higher resolution thumbnail
	originalImageURL := tag.Metadata.Image
	masterURL, err := transformImageURL(auditor, originalImageURL)
	if err != nil {
		return tag, err
	}
	tag.Metadata.ImageMaster = masterURL

	return tag, nil
}

// GetSearch delegates the search operation to either getPopularSearch or getStandardSearch based on settings.Order.
func GetSearch(auditor *audit.Auditor, r *http.Request, settings SearchPageSettings) (*SearchResult, error) {
	if strings.ToLower(settings.Order) == "popular" {
		return getPopularSearch(auditor, r, settings)
	}
	return getStandardSearch(auditor, r, settings)
}

// getPopularSearch handles the popular search logic.
func getPopularSearch(auditor *audit.Auditor, r *http.Request, settings SearchPageSettings) (*SearchResult, error) {
	// Check if popular search is enabled
	if !config.GlobalConfig.PopularSearchEnabled {
		return nil, i18n.Errorf("Popular search is disabled by server configuration.")
	}

	// Perform popular search
	searchArtworks, err := searchPopular(r.Context(), auditor, r, settings, r.Header)
	if err != nil {
		return nil, err
	}

	// Create SearchResult
	result := &SearchResult{
		Artworks: searchArtworks,
		// TODO: populate Popular (the regular one) and RelatedTags
	}

	// Populate thumbnails for each artwork
	for id, artwork := range result.Artworks.Artworks {
		if err := artwork.PopulateThumbnails(auditor); err != nil {
			auditor.SugaredLogger.Errorf("Failed to populate thumbnails for artwork ID %s: %v", id, err)
			return nil, fmt.Errorf("failed to populate thumbnails for artwork ID %s: %w", id, err)
		}
		result.Artworks.Artworks[id] = artwork
	}

	return result, nil
}

// getStandardSearch handles the standard search logic.
func getStandardSearch(auditor *audit.Auditor, r *http.Request, settings SearchPageSettings) (*SearchResult, error) {
	URL := GetSearchArtworksURL(settings.ReturnMap())

	resp, err := API_GET_UnwrapJson(r.Context(), auditor, URL, "", r.Header)
	if err != nil {
		return nil, err
	}

	resp, err = session.ProxyImageUrl(r, resp)
	if err != nil {
		return nil, err
	}

	// Replace "illust", "manga", and "illustManga" with "works" in the JSON response
	//
	// VnPower: IDK how to do better than this lol
	temp := strings.ReplaceAll(string(resp), `"illust"`, `"works"`)
	temp = strings.ReplaceAll(temp, `"manga"`, `"works"`)
	temp = strings.ReplaceAll(temp, `"illustManga"`, `"works"`)

	// Define an intermediate struct to unmarshal the JSON response
	var resultRaw struct {
		ArtworksRaw json.RawMessage `json:"works"`
		Popular     struct {
			Permanent []ArtworkBrief `json:"permanent"`
			Recent    []ArtworkBrief `json:"recent"`
		} `json:"popular"`
		RelatedTags    []string                       `json:"relatedTags"`
		TagTranslation map[string]struct{ En string } `json:"tagTranslation"`
	}

	var artworks SearchArtworks
	var result SearchResult

	// Unmarshal the modified JSON response into the intermediate struct
	err = json.Unmarshal([]byte(temp), &resultRaw)
	if err != nil {
		return nil, err
	}

	// Unmarshal the artworks data
	err = json.Unmarshal([]byte(resultRaw.ArtworksRaw), &artworks)
	if err != nil {
		return nil, err
	}

	// Populate thumbnails for each artwork
	for id, artwork := range artworks.Artworks {
		if err := artwork.PopulateThumbnails(auditor); err != nil {
			auditor.SugaredLogger.Errorf("Failed to populate thumbnails for artwork ID %s: %v", id, err)
			return nil, fmt.Errorf("failed to populate thumbnails for artwork ID %s: %w", id, err)
		}
		artworks.Artworks[id] = artwork
	}

	for id, artwork := range resultRaw.Popular.Permanent {
		if err := artwork.PopulateThumbnails(auditor); err != nil {
			auditor.SugaredLogger.Errorf("Failed to populate thumbnails for artwork ID %s: %v", id, err)
			return nil, fmt.Errorf("failed to populate thumbnails for artwork ID %s: %w", id, err)
		}
		resultRaw.Popular.Permanent[id] = artwork
	}

	for id, artwork := range resultRaw.Popular.Recent {
		if err := artwork.PopulateThumbnails(auditor); err != nil {
			auditor.SugaredLogger.Errorf("Failed to populate thumbnails for artwork ID %s: %v", id, err)
			return nil, fmt.Errorf("failed to populate thumbnails for artwork ID %s: %w", id, err)
		}
		resultRaw.Popular.Recent[id] = artwork
	}

	// Assign artworks and popular data to the result
	result.Artworks = artworks
	result.Popular = resultRaw.Popular

	// Process related tags and translations to populate the RelatedTags slice
	for _, tagName := range resultRaw.RelatedTags {
		tag := TagTag{
			Name: tagName,
		}
		if translation, exists := resultRaw.TagTranslation[tagName]; exists {
			tag.TranslatedName = translation.En
		}
		result.RelatedTags = append(result.RelatedTags, tag)
	}

	return &result, nil
}
